#include "duktape.h"

static duk_ret_t glueFileClose(duk_context *ctx)
{
    duk_push_this(ctx);
    duk_get_prop_string(ctx, -1, "fp");
    int32_t ret = halFileClose(duk_get_pointer(ctx, -1));
    duk_del_prop_string(ctx, 0, "fp");
    duk_push_int(ctx, ret);
    return 1;
}

static duk_ret_t glueFileRead(duk_context *ctx)
{
    short s = duk_get_top(ctx);
    duk_push_this(ctx);
    duk_get_prop_string(ctx, -1, "fp");

    char* buf;
    FILE *fp = duk_get_pointer(ctx, -1);
    if(fp == NULL)
    {
        return DUK_RET_ERROR;
    }

    long lSize;
    if(s == 0)
    {
        long cur = halFileTell(fp);
        halFileSeek(fp, 0, HAL_FILE_SEEK_END);
        lSize = halFileTell(fp) - cur;
        halFileSeek(fp, cur, HAL_FILE_SEEK_SET); 
    } else {
        lSize = (long)duk_get_int(ctx,0);
    }

    buf = (char*)malloc(lSize+1);
    halFileRead(buf, sizeof(char), lSize, fp);
    buf[lSize] = '\0';
    duk_push_string(ctx, buf);
    free(buf);
    return 1;
}

static duk_ret_t glueFileReadLine(duk_context *ctx)
{
    duk_push_this(ctx);
    duk_get_prop_string(ctx, -1, "fp");
    FILE *fp = duk_get_pointer(ctx, -1);
    if(fp == NULL)
    {
        return DUK_RET_ERROR;
    }

    char buf[1024];

    halFileReadLine(buf, 1024, fp);
    duk_push_string(ctx, buf);
    return 1;
}

static duk_ret_t glueFileReadLines(duk_context *ctx)
{
    duk_push_this(ctx);
    duk_get_prop_string(ctx, -1, "fp");
    FILE *fp = duk_get_pointer(ctx, -1);
    if(fp == NULL)
    {
        return DUK_RET_ERROR;
    }

    char buf[1024];

    duk_idx_t arr_idx = duk_push_array(ctx);
    for (int i = 0; halFileReadLine(buf, 1024, fp) != NULL; i++)
    {
        duk_push_string(ctx, buf);
        duk_put_prop_index(ctx, arr_idx, i);
    }
    return 1;
}

static duk_ret_t glueFileWrite(duk_context *ctx)
{
    duk_push_this(ctx);
    duk_get_prop_string(ctx, -1, "fp");
    FILE *fp = duk_get_pointer(ctx, -1);
    if(fp == NULL)
    {
        return DUK_RET_ERROR;
    }

    int32_t ret = halFileWrite(duk_get_string(ctx, 0), fp);
    duk_push_int(ctx, ret);
    return 1;
}

static duk_ret_t glueFileSeek(duk_context *ctx)
{
    duk_push_this(ctx);
    duk_get_prop_string(ctx, -1, "fp");
    FILE *fp = duk_get_pointer(ctx, -1);
    if(fp == NULL)
    {
        return DUK_RET_ERROR;
    }
    int32_t ret = halFileSeek(fp,duk_get_int(ctx, 0),duk_get_int(ctx, 1));
    duk_push_int(ctx, ret);
    return 1;
}

static duk_ret_t glueFileTell(duk_context *ctx)
{
    duk_push_this(ctx);
    duk_get_prop_string(ctx, -1, "fp");
    FILE *fp = duk_get_pointer(ctx, -1);
    if(fp == NULL)
    {
        return DUK_RET_ERROR;
    }
    int32_t ret = halFileTell(fp);
    duk_push_int(ctx, ret);
    return 1;
}

static duk_ret_t glueFileFlush(duk_context *ctx)
{
    duk_push_this(ctx);
    duk_get_prop_string(ctx, -1, "fp");
    FILE *fp = duk_get_pointer(ctx, -1);
    if(fp == NULL)
    {
        return DUK_RET_ERROR;
    }
    int32_t ret = halFileFlush(fp);
    duk_push_int(ctx, ret);
    return 1;
}

/* method */
const duk_function_list_entry object_file_methods[] = {
    { "close", glueFileClose , 0 },
    { "read", glueFileRead, DUK_VARARGS },
    { "readLine", glueFileReadLine , 0 },
    { "readLines", glueFileReadLines , 0 },
    { "write", glueFileWrite , 1 },
    { "seek", glueFileSeek , 2 },
    { "tell", glueFileTell , 0 },
    { "flush", glueFileFlush , 0 },
    { NULL, NULL, 0 }
};

/* Constructor */
static duk_ret_t glueFileFile(duk_context *ctx)
{
    if (!duk_is_constructor_call(ctx)) {
        return DUK_RET_TYPE_ERROR;
    }
    
    const char *filename;
    const char *mode;
    
    filename = duk_to_string(ctx, 0);
    mode = duk_to_string(ctx, 1);

    duk_push_this(ctx);
    duk_push_pointer(ctx, halFileOpen(filename, mode));
    duk_put_prop_string(ctx, -2, "fp");
    duk_dup(ctx, 0);
    duk_put_prop_string(ctx, -2, "filename");
    duk_dup(ctx, 1);
    duk_put_prop_string(ctx, -2, "mode");

    return 0;
}

/* finalizer */
static duk_ret_t glueFileFinalizer(duk_context *ctx)
{
    /* Object being finalized is at stack index 0. */
    if(duk_has_prop_string(ctx, 0, "fp"))
    {
        duk_push_c_function(ctx, glueFileClose, 0);
        duk_dup(ctx, 0);
        duk_call_method(ctx, 0);
        printf("object being finalized!\n");
    }
    return 0;
}

static duk_ret_t glueFileOpen(duk_context *ctx)
{
    duk_push_c_function(ctx,glueFileFile,2);
    duk_push_object(ctx);
    duk_put_function_list(ctx, -1, object_file_methods);
    duk_push_c_function(ctx,glueFileFile,2);
    duk_put_prop_string(ctx,-2,"constructor");
    duk_push_c_function(ctx, glueFileFinalizer, 1);
    duk_set_finalizer(ctx, -2); /* set finalizer */
    duk_put_prop_string(ctx, -2, "prototype");

    duk_dup(ctx,0);
    duk_dup(ctx,1);
    duk_new(ctx,2); /* call constructor */
    return 1;
}

/* unsafe */
static duk_ret_t glueFileRemove(duk_context *ctx)
{
    int32_t ret = halFileRemove(duk_get_string(ctx,0));
    duk_push_int(ctx, ret);
    return 1;
}

static duk_ret_t glueFileExists(duk_context *ctx)
{
    int32_t ret = halFileExists(duk_get_string(ctx,0));
    duk_push_boolean(ctx, ret);
    return 1;
}

/* register */
const duk_function_list_entry module_file_funcs[] = {
    { "open", glueFileOpen , 2 },
    { "remove", glueFileRemove, 1 },
    { "exists", glueFileExists, 1 },
    { NULL, NULL, 0 }
};

const duk_number_list_entry module_file_consts[] = {
    { "EOF", HAL_FILE_EOF },
    { "SEEK_SET", HAL_FILE_SEEK_SET },
    { "SEEK_CUR", HAL_FILE_SEEK_CUR },
    { "SEEK_END", HAL_FILE_SEEK_END },
    { NULL, 0.0 }
};

void module_file_init(duk_context *ctx)
{
    duk_push_object(ctx);
    duk_put_function_list(ctx, -1, module_file_funcs);
    duk_put_number_list(ctx, -1, module_file_consts);
	duk_put_global_string(ctx, "file");
}